package com.qingzhu.component.lock.common.aop.invoker;

import com.qingzhu.component.lock.annotation.LockCommonBeanAnnotation;
import com.qingzhu.component.lock.annotation.business.LockAcquire;
import com.qingzhu.component.lock.annotation.business.LockCustomParam;
import com.qingzhu.component.lock.common.config.ognl.OgnlContextGenerator;
import com.qingzhu.component.lock.common.constant.AutoConfig;
import com.qingzhu.component.lock.common.entity.LockEntity;
import com.qingzhu.component.lock.common.util.BaseUtil;
import com.qingzhu.component.lock.starter.config.LockComponentAutoConfiguration;
import com.qingzhu.component.lock.way.LockExecutor;
import lombok.extern.slf4j.Slf4j;
import ognl.OgnlContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * LockAcquire的aop逻辑，用于在业务方法加锁
 * @author xiangjz
 */
@ConditionalOnClass(LockComponentAutoConfiguration.class)
@Aspect
@Slf4j
@LockCommonBeanAnnotation
public class LockInvokerAop {

    private final LockExecutor lockExecutor;

    public LockInvokerAop(LockExecutor lockExecutor) {
        this.lockExecutor = lockExecutor;
    }

    @Around("@annotation(com.qingzhu.component.lock.annotation.business.LockAcquire)")
    public Object onMethod(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取切入的 Method
        MethodSignature joinPointObject = (MethodSignature) joinPoint.getSignature();
        Method method = joinPointObject.getMethod();

        LockAcquire annotation = method.getAnnotation(LockAcquire.class);

        if(BaseUtil.isEmpty(annotation) || BaseUtil.isEmpty(annotation.key())) {
            throw new IllegalArgumentException("empty lock key set");
        }

        // 获取方法的参数
        Object[] args = joinPoint.getArgs();

        // 获取方法的参数，支持自定义参数值拼接key
        Parameter[] parameters = method.getParameters();
        String finalParamLockKey = "";
        if(!BaseUtil.isEmpty(parameters)) {
            Map<Integer, String> orderParamKeyCache = new HashMap<>(); // 处理当order乱序的时候的缓存
            Queue<Integer> orderQueue = new PriorityQueue<>(); // 用于多个自定义参数的order排序
            // 遍历所有的参数，看是否有 LockCustomParam 注解
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                Object param = args[i];
                LockCustomParam lockCustomParam = parameter.getAnnotation(LockCustomParam.class);
                if(BaseUtil.isEmpty(lockCustomParam)) {
                    continue;
                }
                String key = BaseUtil.retStr(lockCustomParam.key());
                String[] keys = lockCustomParam.keys();
                if(BaseUtil.isEmpty(key) && BaseUtil.isEmpty(keys)) {
                    continue;
                }

                int order = lockCustomParam.order();
                // 这里首先获取一下cache，如果有order是相同的，则直接拼接在上面即可
                String paramLockKey = orderParamKeyCache.getOrDefault(order, "");

                /*
                 * 这里需要判断param的类型，如果是基础数据类型(或者String)，直接获取param即可
                 * 如果是实体对象，需要通过反射调用getter
                 * 如果是Map，调用get
                 * 如果是别的，比如List之类的，暂不支持
                 */
                String paramKeyValue = "";
                Class<?> parameterClass = parameter.getType();

                if(isPrimitiveClass(parameterClass)) {
                    paramKeyValue = BaseUtil.retStr(param);
                    if(!BaseUtil.isEmpty(paramKeyValue)) {
                        paramKeyValue += AutoConfig.COMPONENT_LOCK_KEY_SPLIT_CHARACTER;
                    }
                } else if(param instanceof Collection) {
                    // 暂时不支持集合类型的参数
                    throw new IllegalArgumentException("unsupported parameter type for lock key");
                } else {
                    // 构建一个OgnlContext对象
                    OgnlContext context = OgnlContextGenerator.generate(param);
                    paramKeyValue += OgnlContextGenerator.getValue(context, key);
                    if(!BaseUtil.isEmpty(paramKeyValue)) {
                        paramKeyValue += AutoConfig.COMPONENT_LOCK_KEY_SPLIT_CHARACTER;
                    }
                    if(!BaseUtil.isEmpty(keys)) {
                        for (String s : keys) {
                            if(BaseUtil.isEmpty(s)) {
                                continue;
                            }
                            paramKeyValue += OgnlContextGenerator.getValue(context, s);
                            if(!BaseUtil.isEmpty(paramKeyValue)) {
                                paramKeyValue += AutoConfig.COMPONENT_LOCK_KEY_SPLIT_CHARACTER;
                            }
                        }
                    }
                }

                paramLockKey += paramKeyValue;
                orderParamKeyCache.put(order, paramLockKey);
                if(!orderQueue.contains(order)) {
                    orderQueue.offer(order);
                }
            }
            // 这里就是最后的lockKey的计算拼接了
            if(!orderQueue.isEmpty() && !orderParamKeyCache.isEmpty()) {
                while(!orderQueue.isEmpty()) {
                    int order = orderQueue.poll();
                    String key = orderParamKeyCache.getOrDefault(order, "");
                    finalParamLockKey += key;
                }
            }
            if(!BaseUtil.isEmpty(finalParamLockKey)) {
                finalParamLockKey = finalParamLockKey.substring(0, finalParamLockKey.length() - AutoConfig.COMPONENT_LOCK_KEY_SPLIT_CHARACTER.length());
            }
        }

        final LockEntity[] lockEntity = {null};
        lockEntity[0] = lockExecutor.lock(annotation.key() + AutoConfig.COMPONENT_LOCK_KEY_SPLIT_CHARACTER + finalParamLockKey, annotation.expireOnForkingFull());
        /*
         * 这里为了让InterruptException生效，不让它被上层的try/catch捕获，这里直接通过new一个线程来执行业务方法
         * 通过future.get阻塞获取返回值
         */
        FutureTask f = new FutureTask(() -> {
            try {
                lockEntity[0].setExpireTime(annotation.expire());
                lockEntity[0].setStrategyOnExpired(annotation.strategyOfExpired());
                lockEntity[0].setT(null);
                return joinPoint.proceed();
            } catch (Throwable e) {
                e.printStackTrace();
                log.error("{} business of lock key '{}' execute failed, cause of {}", AutoConfig.COMPONENT_LOCK_LOG_PREFIX, lockEntity[0].getLockKey(), e);
                lockEntity[0].setT(e);
                return lockEntity[0];
            }
        });
        Thread businessThread = new Thread(f);
        Object o;
        try {
            businessThread.start();
            if(annotation.expire() < 0) {
                o = f.get();
            } else {
                o = f.get(annotation.expire() + 50L, TimeUnit.MILLISECONDS);
            }
        } catch (TimeoutException e) {
            if(annotation.stopOnExpired()) {
                // 立即停止尚未完成的业务逻辑
                f.cancel(false);
                businessThread.stop();
            }
            // 超时还没有返回结果
            LockExecutor.doOnExpired(lockEntity[0]);
            // 默认返回空
            return null;
        } catch (ExecutionException e) {
            throw e.getCause();
        } finally {
            if(!BaseUtil.isEmpty(lockEntity[0])) {
                lockEntity[0].unlock();
            }
        }

        if(o instanceof LockEntity) {
            LockEntity lock = (LockEntity) o;
            if(!BaseUtil.isEmpty(lock.getT())) {
                throw lock.getT();
            }
            return null;
        } else {
            return o;
        }
    }

    /**
     * 判断是否是基本类型
     * @param clz
     * @return
     */
    private boolean isPrimitiveClass(Class clz) {
        try {
            return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return clz.isPrimitive();
        }
    }

}
